<?php
/**
 * 	Bushido
 *
 * @category   Bushido
 * @package    Bushido_Table
 * @copyright  Copyright (c) 2008-2009 Nathan Keyes
 * @author Nathan Keyes
 * @version    $Id: Abstract.php 60 2009-11-15 19:01:52Z nkeyes $
 */
 
/**
 * 
 * @author Nathan Keyes
 *
 */
abstract class Bushido_Db_Table_Abstract extends Bushido_Db_Table_Generic implements Zend_Acl_Resource_Interface, Zend_Acl_Assert_Interface 
{
	CONST ZERO_OR_ONE = 'ZERO_OR_ONE';
	CONST ZERO_OR_MANY = 'ZERO_OR_MANY';
	CONST ONE = 'ONE';
	CONST ONE_OR_MANY = 'ONE_OR_MANY';
	CONST MANY = 'MANY';
	
	CONST ALLOW_ADAPTER_OVERRIDE = 'allowAdapterOverride';
	
	
	protected $_niceName;
	protected $_allowedRoles = array();
	protected $_searchableFields = array();
	protected $_childMap = array();
	protected $_form;
	protected $_subForm;
	protected $_simultaneousSteps;
	protected $_gridLayout;
	protected $_extraGridFields;
	
	protected $_rowsetClass = 'Bushido_Db_Table_Rowset';
	protected $_rowClass = 'Bushido_Db_Table_Row';
	
	protected $_cloneOverrides = array();
	
	
	public function __construct($config = array())
	{
		if(!is_array($config))
		{
			$config = array(self::ADAPTER => $config);
		}
			
		if($config[self::ALLOW_ADAPTER_OVERRIDE])
		{
			if(!isset($config[self::ROW_CLASS]))
			{
				$config[self::ROW_CLASS] = 'Bushido_Db_Table_Row';
			}
		}
		else
		{
			
			$adapter = Bushido_App::getAdapter(); //forcibly use the default adapter incase of cross-DB work
			if(!is_array($config))
			{
				$config = array(self::ADAPTER => $adapter);
			}
			else
			{
				$config[self::ADAPTER] = $adapter; 
			}
			if(!isset($config[self::ROW_CLASS]))
			{
				$config[self::ROW_CLASS] = 'Bushido_Db_Table_Row';
			}
		}
		parent::__construct($config);
	}

	
	/**
	 * 
	 * @return array
	 */
	//public abstract function getAllowedRoles();
	
	
	/**
	 * 
	 * @return array
	 */
	//public abstract function getSearchableFields();
	
	/**
	 * 
	 * @return void
	 */
	
	protected function _setFieldDetails()
	{
		
	}
	
	/**
	 * (non-PHPdoc)
	 * @see library/Zend/Db/Table/Zend_Db_Table_Abstract#init()
	 */
	public function init()
	{
		$this->_metadata = $this->info('metadata');
		$this->_setFieldDetails();
		if(!isset($this->_gridLayout) || empty($this->_gridLayout))
		{
			$this->_gridLayout = array();
			foreach($this->_getCols() as $field)
			{
				if(stripos($this->_metadata[$field]['DATA_TYPE'], 'blob') === false && stripos($this->_metadata[$field]['DATA_TYPE'], 'bin') === false)
				{
					$this->_gridLayout[] = array
					(
						'key' => $field,
						'label' => $field,
						'sortable' => true,
						//'width' => 'auto'
					);
				}
				
			}
		}
	}
	
	/**
	 * 
	 * @return string
	 */
	public function getName()
	{
		return $this->_name;
	}
	
	/**
	 * (non-PHPdoc)
	 * @see library/Zend/Db/Table/Zend_Db_Table_Abstract#insert()
	 */
	public function insert($data)
	{
		$data = $this->_filterUnknownFields($data);
		$this->_nullifyEmptyValues($data);
		$retVal = parent::insert($data);
		if(!is_array($retVal))
		{
			$pk = array
			(
				current($this->info(self::PRIMARY)) => $retVal
			);
		}
		else
		{
			$pk = $retVal;
		}
		
		//Zend_Debug::dump($pk);exit;
		
		if(!$this instanceof Bushido_DbTable_AuditLog)
		{
			$auditLog = Bushido_Db_Table::getTable('Bushido_DbTable_AuditLog');
			foreach($data as $field => $value)
			{
				$auditLog->insert
				(
					array
					(
						'bushido_user_id' => Bushido_App::session()->userInfo['bushido_user_id'],
						'type' => 'insert',
						'table_name' => $this->_name,
						'pk' => Bushido_Db_Table_Row::makePKString($pk),
						'field' => $field,
						'new_value' => $value,
						'timestamp' => new Zend_Db_Expr('NOW()')
					)
				);
			}
		}
		return $retVal;
	}
	
	
	/**
	 * (non-PHPdoc)
	 * @see library/Zend/Db/Table/Zend_Db_Table_Abstract#update()
	 */
	public function update($data, $where)
	{
		$rows = $this->fetchAll($where);
		$pk = current($this->info(self::PRIMARY));
		$data = $this->_filterUnknownFields($data);
		$this->_nullifyEmptyValues($data);
		$retVal = 0;
		
		foreach($rows as $row)
		{
			$where = $this->getAdapter()->quoteInto("$pk = ?", $row->$pk);
		
			foreach($data as $field => $value)
			{
				if($row->$field == $value)
				{
					unset($data[$field]); // don't update if the value is the same
				}
			}
			if(count($data))
			{
				$retVal += parent::update($data, $where);
				//Zend_Debug::dump($retVal);
				if(!$this instanceof Bushido_DbTable_AuditLog)
				{	
					$auditLog = Bushido_Db_Table::getTable('Bushido_DbTable_AuditLog');
					foreach($data as $field => $value)
					{
						$auditLog->insert
						(
							array
							(
								'bushido_user_id' => Bushido_App::session()->userInfo['bushido_user_id'],
								'type' => 'update',
								'table_name' => $this->_name,
								'pk' => $row->getPKString(),
								'field' => $field,
								'old_value' => $row->$field,
								'new_value' => $value,
								'timestamp' => new Zend_Db_Expr('NOW()')
							)
						);
					}
				}
			}
		}
		//exit;
		return $retVal;
	}
	
	
	/**
	 * (non-PHPdoc)
	 * @see library/Zend/Db/Table/Zend_Db_Table_Abstract#delete()
	 */
	public function delete($where)
	{
		$rows = $this->fetchAll($where);
		$pk = current($this->info(self::PRIMARY));
		
		foreach($rows as $row)
		{
			$where = $this->getAdapter()->quoteInto("$pk = ?", $row->$pk);
			if(!$this instanceof Bushido_DbTable_AuditLog)
			{	
				$auditLog = Bushido_Db_Table::getTable('Bushido_DbTable_AuditLog');
				foreach($row->toArray() as $field => $value)
				{
					$auditLog->insert
					(
						array
						(
							'bushido_user_id' => Bushido_App::session()->userInfo['bushido_user_id'],
							'type' => 'delete',
							'table_name' => $this->_name,
							'pk' => $row->getPKString(),
							'field' => $field,
							'old_value' => $value,
						)
					);
				}
			}
			//Zend_Debug::dump($where);exit;
			$retVal += parent::delete($where);
		}
		return $retVal;
	}
	
	
	/**
	 * (non-PHPdoc)
	 * @see library/Zend/Acl/Resource/Zend_Acl_Resource_Interface#getResourceId()
	 */
	public function getResourceId()
	{
		return get_class($this);
	}
	
	
	/**
	 * (non-PHPdoc)
	 * @see library/Zend/Acl/Assert/Zend_Acl_Assert_Interface#assert()
	 */
	public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $role = null, Zend_Acl_Resource_Interface $resource = null, $privilege = null)
	{
		$class = get_class($this);
		if(((!$resource instanceof $class) && (!$resource instanceof Bushido_Db_Table_Row)) || ($resource instanceof Bushido_Db_Table_Row && $resource->getTableClass() !== $class))
		{
			return false;
		}
		
		$assertFunction = 'assert' . ucfirst($privilege);
		
		if(!method_exists($this, $assertFunction))
		{
			return false;
		}
		
		$isAdmin = Bushido_DbTable_User::hasRole($role->bushido_user_id, Bushido_DbTable_Role::ADMIN);
		//Zend_Debug::dump($isAdmin, '$isAdmin:');
		
		$privilegeAllowed = call_user_func(array($this, $assertFunction), $role, $resource);
		//Zend_Debug::dump($privilegeAllowed, '$privilegeAllowed:');
		
		$allowed = $isAdmin || $privilegeAllowed;
		//Zend_Debug::dump($allowed, '$allowed:');
		
		return $allowed;
	}
	
	/**
	 * 
	 * @param $role
	 * @param $resource
	 * @return boolean
	 */
	public function assertCreate(Zend_Acl_Role_Interface $role = null, Zend_Acl_Resource_Interface $resource = null)
	{
		return true;
	}
	
	
	/**
	 * 
	 * @param $role
	 * @param $resource
	 * @return boolean
	 */
	public function assertView(Zend_Acl_Role_Interface $role = null, Zend_Acl_Resource_Interface $resource = null)
	{
		/*
		if(!$role || !$resource)
		{
			return false;
		}
		return $role->bushido_user_id == $resource->getCreatorId();
		*/
		return true;
		
	}
	
	/**
	 * 
	 * @param $role
	 * @param $resource
	 * @return boolean
	 */
	public function assertUpdate(Zend_Acl_Role_Interface $role = null, Zend_Acl_Resource_Interface $resource = null)
	{
		if(!$role || !$resource)
		{
			return false;
		}
		return $role->bushido_user_id == $resource->getCreatorId();
	}
	
	/**
	 * 
	 * @param $role
	 * @param $resource
	 * @return boolean
	 */
	public function assertDelete(Zend_Acl_Role_Interface $role = null, Zend_Acl_Resource_Interface $resource = null)
	{
		if(!$role || !$resource)
		{
			return false;
		}
		return $role->bushido_user_id == $resource->getCreatorId();
	}
	
	
	/**
	 * 
	 * @param $acl
	 * @return unknown_type
	 */
	public function setAccess(Zend_Acl $acl)
	{
		/*
		if(!$acl->has($this->getResourceId()))
		{
			$acl->add(new Zend_Acl_Resource($this->getResourceId()));
			$roles = $this->getAllowedRoles();
			//var_dump($roles);exit(0);
			if(is_array($roles))
			{
				foreach($roles as $role)
				{
					$acl->allow($role, $this->getResourceId());
				}
				unset($role);
			}
			else
			{
				$acl->allow($roles, $this->getResourceId());
			}
		}
		*/
	}
	
	/**
	 * 
	 * @param $row
	 * @return string
	 */
	public static function getDisplayValue(Zend_Db_Table_Row_Abstract $row, $context = null)
	{		
		$value = '';
		$table = $row->getTable();
		foreach($table->info('primary') as $primary)
		{
			$value .= ' ' . $primary . ': ' . $row->$primary;
		}
		unset($primary);
		return $value;
	}
	
	
	/**
	 * 
	 * @return array
	 */
	public function getChildMap()
	{
		return $this->_childMap;
	}
	
	/**
	 * 
	 * @return string
	 */
	public function getNiceName()
	{
		return $this->_niceName;
	}
	
	public function setNiceName($niceName)
	{
		$this->_niceName = (string) $niceName;
	}
	
	/**
	 * 
	 * @return array
	 */
	public function getReferenceMap()
	{
		return $this->_referenceMap;
	}
	
	/**
	 * 
	 * @return Bushido_Db_Form
	 */
	public function getForm($pk = array())
	{
		//echo __FILE__ . ' line ' . __LINE__ . "<br />\n";
		if(!isset($this->_form))
		{
		//var_dump($pk);exit;
		if(!empty($pk))
			{
				//echo("found " . get_class($this) . " $pk<br />\n");
				
				$row = call_user_func_array(array($this, 'find'), array_values($pk))->current();
				if(!$row)
				{
					throw new Bushido_Db_Table_Exception_RowNotFound();
				}
			}
			else
			{
				//echo("did not find " . get_class($this) . " $pk<br />\n");
				$row = $this->fetchNew();
				//echo __FILE__ . ' line ' . __LINE__ . "<br />\n";
			}
			
			$this->_form = new Bushido_Db_Form($row);
			//echo __FILE__ . ' line ' . __LINE__ . "<br />\n";
		}
		return $this->_form;
	}
	
	/**
	 * 
	 * @return Bushido_Db_Form_SubForm
	 */
	public function getSubForm($pk = null)
	{
		if(!isset($this->_subForm))
		{
		if($pk)
			{
				$row = $this->find($pk)->current();
			}
			else
			{
				$row = $this->fetchNew();
			}
			
			$this->_subForm = new Bushido_Db_Form_SubForm($row);
		}
		
		return $this->_subForm;
	}
	
	/**
	 * 
	 * @param $params
	 * @return Zend_Db_Row_Abstract
	 */
	public function search($where, $sortColumn = null, $sortDirection = null, $limit = null, $offset = 0, &$totalRecords = null)
	{
		if(is_array($where))
		{
			$where = $this->_filterUnknownFields($where);
			//Zend_Debug::dump($params);
			$select = $this->select();
			
			
			foreach($where as $key => $value)
			{
				if(!is_numeric($key))
				{
					if($value == null)
					{
						$select = $select->where("$key IS NULL");
					}
					elseif(!is_numeric($value) && intval($value) == 0)
					{
						$select = $select->where("$key LIKE ?", $value);
					}
					else
					{
						$select = $select->where("$key = ?", $value);
					}
				}
				else
				{
					$select = $select->where($value);
				}
			}
		}
		else
		{
			$select = $this->select()->where((string) $where);
		}
		$countSelect = str_ireplace('"' . $this->_name . '".*', 'count(*)', "$select");
		$totalRecords = $select->getAdapter()->fetchOne($countSelect);
		if($sortColumn)
		{
			if($sortDirection)
			{
				$select = $select->order("$sortColumn $sortDirection");
			}
			else
			{
				$select = $select->order("$sortColumn");				
			}
			
		}
		
		if($limit)
		{
			$select = $select->limit($limit, $offset);
		}
		
		
		
		//Zend_Debug::dump("$select");exit;
		return $this->fetchAll($select);
	}
	
	protected function _nullifyEmptyValues(&$data)
	{
		if(is_array($data))
		{
			foreach($data as $key => $value)
			{
				$defaultValue = isset($this->_metadata[$key]['fieldDetails']) && isset($this->_metadata[$key]['fieldDetails']['defaultValue']) ? $this->_metadata[$key]['fieldDetails']['defaultValue'] : new Zend_Db_Expr('null');
				
				if(!$value instanceof Zend_Db_Expr)
				{
					$value = trim($value);
					$data[$key] = $value == '' /*|| $value == '0'*/ ? $defaultValue : $value;
				}
				if(in_array(strtolower(trim("$value")), array('null', '')))
				{
					unset($data[$key]);
				}
				
			}
			unset($key);
			unset($value);
		}
	}
	
	
	/**
	 * 
	 * @param $row the row to clone
	 * @param $overrides array of values to override in cloned row
	 * @return Bushido_Db_Table_Row
	 */
	public function duplicate(Zend_Db_Table_Row_Abstract $row, array $overrides = null)
	{
		$rowArray = $row->toArray();
		$primaryKeys = $this->info(Zend_Db_Table::PRIMARY);
		if(!is_array($overrides))
		{
			$overrides = array();
		}
		foreach($primaryKeys as $pk)
		{
			unset($rowArray[$pk]);
		}
		
		$rowArray = array_merge($rowArray, $this->_filterUnknownFields($overrides));
		$newPK = $this->insert($rowArray);
		
		$newRow = $this->find($newPK)->current();
		
		if(!is_array($overrides))
		{
			$overrides = array();
		}
		
		
		$classesToClone = array();
		
		foreach($this->_childMap as $childLabel => $childReferenceData)
		{
			if($childReferenceData['duplicate'])
			{
				$classesToClone[$childReferenceData['refTableClass']] = 1;
			}
		}
		unset($pk);
		
		$overrides = array_merge($overrides, $this->_cloneOverrides, array(current($this->info(Zend_Db_Table::PRIMARY)) => $newPK));
		
		foreach($classesToClone as $childClass => $val)
		{
			$findFunction = 'find' . $childClass;
			$table = Bushido_Db_Table::getTable($childClass);
			foreach($row->$findFunction() as $childRow)
			{
				$table->duplicate($childRow, $overrides);
			}
			unset($childRow);
		}
		unset($childClass);
		unset($val);
		return $newRow;
	}
	
	/**
	 * 
	 * @param $table
	 * @param $data
	 * @return array
	 */
	protected function _filterUnknownFields($data)
	{

		$row = $this->fetchNew()->toArray();

		return array_intersect_key($data, $row);
	}
	
	public function getdefaultValues()
	{
		$defaultValues = array();
		foreach($this->info('metadata') as $field => $fieldData)
		{
			if(isset($fieldData['fieldDetails']) && is_array($fieldData['fieldDetails']) && !empty($fieldData['fieldDetails']['defaultValue']))
			{
				$defaultValues[$field] = $fieldData['fieldDetails']['defaultValue'];
			}
		}
		unset($field);
		unset($fieldData);
		return $defaultValues;
	}
	
	
	/**
	 * 
	 * @param $data
	 * @return array
	 */
	public static function formatDataForGrid($data)
	{
		if(is_array($data))
		{
			return self::formatArrayForGrid($data);
		}
		elseif($data instanceof Zend_Db_Table_Rowset_Abstract)
		{
			return self::formatRowsetForGrid($data);
		}
		else
		{
			throw new Exception("$data must be an array or an instance of Zend_Db_Table_Rowset_Abstract.");
		}
	}
	
	/**
	 * 
	 * @param $rowset
	 * @return array
	 */
	public static function formatRowsetForGrid(Zend_Db_Table_Rowset_Abstract $rowset)
	{
		return self::formatArrayForGrid($rowset->toArray());
	}
	
	
	/**
	 * 
	 * @param $rowset
	 * @return array
	 */
	public static function formatArrayForGrid(array $dataset)
	{
		$formattedDatasetArray = array();
		
		if(count($dataset))
		{
			//var_dump($rowset->count());
			foreach($dataset as $record)
			{
				/*
				$record = Bushido_Db_Table::getTable('record_Model_DbTable_record')->find($record['record_id'])->current();
				if(!$record)
				{
					continue;
				}
				
				*/
				if(!is_array($record))
				{
					if(!method_exists($record, 'toArray'))
					{
						throw new Exception('$record passed to ' . __CLASS__ . '::' . __FUNCTION__ . ' must be an array.');
					}
					else
					{
						$recordArray = $record->toArray();
					}
					
				}
				else
				{
					$recordArray = $record;
				}
				$formattedDatasetArray[] = $recordArray;
			}
		}
		return $formattedDatasetArray;
	}
	
	
	/**
	 * 
	 * @return array
	 */
	public function getSimultaneousSteps()
	{
		return $this->_simultaneousSteps;
	}
	
	/**
	 * 
	 * @param $simultaneousSteps
	 * @return void
	 */
	public function setSimultaneousSteps($simultaneousSteps)
	{
		$this->_simultaneousSteps = $simultaneousSteps;
	}
	
	/**
	 * 
	 * @return array
	 */
	public function getGridLayout()
	{
		return $this->_gridLayout;
	}
	
	/**
	 * 
	 * @param $simultaneousSteps
	 * @return void
	 */
	public function setGridLayout($gridLayout)
	{
		$this->_gridLayout = $gridLayout;
	}
	
	
	/**
	 * 
	 * @return array
	 */
	public function getExtraGridFields()
	{
		return $this->_extraGridFields;
	}
	
	/**
	 * 
	 * @param $simultaneousSteps
	 * @return void
	 */
	public function setExtraGridFields($extraGridFields)
	{
		$this->_extraGridFields = $extraGridFields;
	}
	
}